package net.osin.crypto

import scala.collection.mutable.ArrayBuffer

object SM3 {
  private val IV: Array[Int] = Array(0x7380166f, 0x4914b2b9, 0x172442d7, 0xda8a0600, 0xa96f30bc, 0x163138aa, 0xe38dee4d, 0xb0fb0e4e)
  private val T15: Int = 0x79cc4519
  private val T63: Int = 0x7a879d8a

  private def FF15(x: Int, y: Int, z: Int): Int = x ^ y ^ z

  private def FF63(x: Int, y: Int, z: Int): Int = (x & y) | (x & z) | (y & z)

  private def GG15(x: Int, y: Int, z: Int): Int = x ^ y ^ z

  private def GG63(x: Int, y: Int, z: Int): Int = (x & y) | (~x & z)

  private def rotateLeft(i: Int, distance: Int): Int = (i << distance) | (i >>> -distance)

  private def P0(x: Int): Int = x ^ rotateLeft(x, 9) ^ rotateLeft(x, 17)

  private def P1(x: Int): Int = x ^ rotateLeft(x, 15) ^ rotateLeft(x, 23)

  private def padding(source: Array[Byte]): Array[Int] = {
    val l = source.length * 8
    var k = 448 - (l + 1) % 512
    if (k < 0) k += 512
    val kArr = new Array[Byte](k / 8)
    val pArr = new Array[Int]((l + 1 + k + 64) / 32)
    val tmp = source ++ Array(0x80.toByte) ++ kArr ++ endbytes(l)
    for (i <- 0 until (l + 1 + k + 64) / 32) {
      pArr(i) = toInteger(tmp, i)
    }
    pArr
  }

  private def endbytes(l: Long) = {
    val bytes = new Array[Byte](8)
    for (i <- 0 until 8) {
      bytes(i) = (l >>> ((7 - i) * 8)).toByte
    }
    bytes
  }

  private def toInteger(_bytes: Array[Byte], index: Int): Int = {
    val _offset = index * 4
    val b0 = _bytes(_offset) & 0xff
    val b1 = _bytes(_offset + 1) & 0xff
    val b2 = _bytes(_offset + 2) & 0xff
    val b3 = _bytes(_offset + 3) & 0xff
    (b0 << 24) | (b1 << 16) | (b2 << 8) | b3
  }

  private def CF(vi: Array[Int], bi: Array[Int]): Array[Int] = {
    var ss1: Int = 0
    var ss2: Int = 0
    var tt1: Int = 0
    var tt2: Int = 0
    var a = vi(0)
    var b = vi(1)
    var c = vi(2)
    var d = vi(3)
    var e = vi(4)
    var f = vi(5)
    var g = vi(6)
    var h = vi(7)
    val w: Array[Int] = new Array[Int](68)
    val w1: Array[Int] = new Array[Int](64)
    for (i <- 0 until 16) {
      w(i) = bi(i)
    }
    for (j <- 16 until 68) {
      w(j) = P1(w(j - 16) ^ w(j - 9) ^ rotateLeft(w(j - 3), 15)) ^ rotateLeft(w(j - 13), 7) ^ w(j - 6)
    }
    for (j <- 0 until 64) {
      w1(j) = w(j) ^ w(j + 4)
    }

    for (j <- 0 until 16) {
      ss1 = rotateLeft(rotateLeft(a, 12) + e + rotateLeft(T15, j), 7)
      ss2 = ss1 ^ rotateLeft(a, 12)
      tt1 = FF15(a, b, c) + d + ss2 + w1(j)
      tt2 = GG15(e, f, g) + h + ss1 + w(j)
      d = c
      c = rotateLeft(b, 9)
      b = a
      a = tt1
      h = g
      g = rotateLeft(f, 19)
      f = e
      e = P0(tt2)
    }
    for (j <- 16 until 64) {
      ss1 = rotateLeft(rotateLeft(a, 12) + e + rotateLeft(T63, j), 7)
      ss2 = ss1 ^ rotateLeft(a, 12)
      tt1 = FF63(a, b, c) + d + ss2 + w1(j)
      tt2 = GG63(e, f, g) + h + ss1 + w(j)
      d = c
      c = rotateLeft(b, 9)
      b = a
      a = tt1
      h = g
      g = rotateLeft(f, 19)
      f = e
      e = P0(tt2)
    }
    val v: Array[Int] = Array(a, b, c, d, e, f, g, h)
    for (i <- 0 until 8) v(i) = v(i) ^ vi(i)
    v
  }



  def hash(source: Array[Byte]): Array[Byte] = {
    if (source.length > 16_000_000) return Array(0xef.toByte)
    val m1: Array[Int] = padding(source)
    val n: Int = m1.length / (512 / 32)
    var b: Array[Int] = null
    var vi: Array[Int] = IV
    var vi1: Array[Int] = null
    for (i <- 0 until n) {
      b = m1.slice(i * 16, (i + 1) * 16)
      vi1 = CF(vi, b)
      vi = vi1
    }
    val r = new ArrayBuffer[Byte]()
    vi1.foreach(f => r.appendAll(Exchange.toByteArray(f)))
    r.toArray
  }

  def hash(source: LazyList[Byte]): Array[Byte] = {
    val b: Array[Int] = new Array[Int](16)
    var vi: Array[Int] = IV
    var vi1: Array[Int] = null
    var slength = 0L
    val m1: Array[Byte] = new Array[Byte](64)
    for (n <- source) {
      if (slength % 64 == 63) {
        m1((slength % 64).toInt) = n
        for (i <- 0 until 16) {
          b(i) = toInteger(m1, i)
        }
        vi1 = CF(vi, b)
        vi = vi1
      }
      m1((slength % 64).toInt) = n
      slength += 1
    }
    val sl = (slength % 64).toInt
    val l = slength * 8
    var k = 448 - (sl * 8 + 1) % 512
    if (k < 0) k += 512
    val kArr = new Array[Byte](k / 8)
    val pArr = new Array[Int]((sl * 8 + 1 + k + 64) / 32)
    val tmp = m1.take(sl) ++ Array(0x80.toByte) ++ kArr ++ endbytes(l)
    for (i <- 0 until (sl * 8 + 1 + k + 64) / 32) {
      pArr(i) = toInteger(tmp, i)
    }

    for (i <- 0 until pArr.length / (512 / 32)) {
      vi1 = CF(vi, pArr.slice(i * 16, (i + 1) * 16))
      vi = vi1
    }
    val r = new ArrayBuffer[Byte]()
    vi1.foreach(f => r.appendAll(Exchange.toByteArray(f)))
    r.toArray
  }

}
